﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Cloner.cs" company="Elilink">
//   Defor
// </copyright>
// <summary>
//   Class for creating deep copy of an object
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace BusinessLayer.Tools
{
    using System;
    using System.Reflection;

    /// <summary>
    ///     Class for creating deep copy of an object
    /// </summary>
    public static class Cloner
    {
        #region Public Methods and Operators

        /// <summary>
        /// Perform a deep Copy of the object using field-based Reflection
        /// </summary>
        /// <typeparam name="T">
        /// The type of instance to be copied
        /// </typeparam>
        /// <param name="obj">
        /// The object to be copied.
        /// </param>
        /// <returns>
        /// Deep copy of the same type.
        /// </returns>
        public static T DeepCopy<T>(this T obj) where T : class
        {
            if (obj == null)
            {
                return null;
            }

            return (T)Process(obj);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The process.
        /// </summary>
        /// <param name="object">
        /// The object.
        /// </param>
        /// <returns>
        /// The <see cref="object"/>.
        /// </returns>
        /// <exception cref="ArgumentException">Is thrown if type is unknown
        /// </exception>
        private static object Process(object @object)
        {
            if (@object == null)
            {
                return null;
            }

            Type type = @object.GetType();
            if (type.IsValueType || type == typeof(string))
            {
                return @object;
            }

            if (type.IsArray)
            {
                string fullTypeName = type.FullName.Replace("[]", string.Empty);
                if (type.AssemblyQualifiedName != null)
                {
                    fullTypeName = type.AssemblyQualifiedName.Replace("[]", string.Empty);
                }

                Type elementType = Type.GetType(
                    fullTypeName, 
                    Assembly.Load, 
                    (assem, name, ignore) =>
                    assem == null ? Type.GetType(name, false, ignore) : assem.GetType(name, false, ignore), 
                    true);

                var array = (Array)@object;

                if (elementType != null)
                {
                    Array copied = Array.CreateInstance(elementType, array.Length);
                    for (int i = 0; i < array.Length; i++)
                    {
                        copied.SetValue(Process(array.GetValue(i)), i);
                    }

                    return Convert.ChangeType(copied, @object.GetType());
                }

                return null;
            }

            if (type.IsClass)
            {
                object toret = Activator.CreateInstance(@object.GetType());
                FieldInfo[] fields = type.GetFields(
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                foreach (FieldInfo field in fields)
                {
                    object fieldValue = field.GetValue(@object);
                    if (fieldValue == null)
                    {
                        continue;
                    }

                    field.SetValue(toret, Process(fieldValue));
                }

                return toret;
            }

            throw new ArgumentException("Unknown type");
        }

        #endregion
    }
}